// imports necessary for openGL
import processing.opengl.*;
import javax.media.opengl.*;

// import necessary for GLGraphics lib
import codeanticode.glgraphics.*;

// import the TUIO library for multitouch support
import TUIO.*;

// create a TUIO client to recieve multitouch events
TuioProcessing tuioClient;

// some important global vars
int screenWidth      = 640;
int screenHeight     = 360;
float [] lightPos    = {0.0,0.0,40.0,1.0};

// high-level graphics objects
GL                   gl;
GLGraphicsOffScreen  glos;
GLSLShader           shaderPost;
GLSLShader           shaderBump;
GLSLShader           shaderBloom;
GLTexture            tex0;
GLTexture            tex1;

// shader memory locations for sending data from the application to the shaders
int        texUnit0Loc  = 0;
int        elapsedTimeLoc = 0;

int        locAttTangentVec  = 0;
int        locUniColorMap    = 0;
int        locUniNormalMap   = 0;
int        locUniInvRadius   = 0;

int        locBloomColorMap  = 0;


// variables to be uploaded to shader memory for application-shader data communication
float      elapsedTime = 0.0;

float []   vTangent  = new float[3];
float      invRadius = 0.0;

void setup()
{  
  // standard setup, but passing in GLGRAPHICS constant, which allows faster texture rendering under OpenGL
  size(screenWidth, screenHeight, GLConstants.GLGRAPHICS);
  
  // initialize our off-screen rendering surface. the reason this is used is to do all our rendering
  // to a separate surface (or texture) and then in the final render pass, render the surface onscreen,
  // and apply all post-processing shaders, at once, to the final surface.
  glos  = new GLGraphicsOffScreen(this, screenWidth, screenHeight);
  
  // grab a hold of our OpenGL graphics context
  gl = ((PGraphicsOpenGL)g).gl;
  
  // some useful information to output
  println("OpenGL version: " + gl.glGetString(GL.GL_VERSION));
  println("GLSL version: " + gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION_ARB));
   
  // create light components
  float [] ambLight = { 0.2, 0.2, 0.2, 1.0 };
  float [] diffuseLight = { 0.8, 0.8, 0.8, 1.0 };
  float [] specularLight = { 0.5, 0.5, 0.5, 1.0 };
   
  // setup light 0 parameters
  gl.glLightfv(gl.GL_LIGHT0, gl.GL_AMBIENT, ambLight, 0);
  gl.glLightfv(gl.GL_LIGHT0, gl.GL_DIFFUSE, diffuseLight, 0);
  gl.glLightfv(gl.GL_LIGHT0, gl.GL_SPECULAR, specularLight, 0);
  gl.glLightfv(gl.GL_LIGHT0, gl.GL_POSITION, lightPos, 0);
  
  // load our textures
  tex0 = new GLTexture(this, "goldfish.jpg");
  tex1 = new GLTexture(this, "normal_map.png");
  
  // shader loading and compilation code (fragment shader is another name for a pixel shader).
  // since we currently don't need any shaders applied to the vertices, we don't need to load
  // a vertex shader, and can let the fixed-function pipeline handle the vertices.
  shaderPost = new GLSLShader(this);
  shaderPost.loadFragmentShader("wavy.frag");
  shaderPost.linkProgram();
  
  shaderBump = new GLSLShader(this);
  shaderBump.loadVertexShader("bump.vert");
  shaderBump.loadFragmentShader("bump.frag");
  shaderBump.linkProgram();
  
  shaderBloom = new GLSLShader(this);
  shaderBloom.loadFragmentShader("bloom.frag");
  shaderBloom.linkProgram();
  
  // retrieve and store the memory locations of the shader variables to be fed from our application
  
  // post-processing shader
  texUnit0Loc         = shaderPost.getUniformLocation("tex0");
  elapsedTimeLoc      = shaderPost.getUniformLocation("elapsed_time");
  
  // bump-mapping shader
//  locAttTangentVec    = shaderBump.getUniformLocation("vTangent");
  locUniColorMap      = shaderBump.getUniformLocation("colorMap");
  locUniNormalMap     = shaderBump.getUniformLocation("normalMap");
  locUniInvRadius     = shaderBump.getUniformLocation("invRadius");
  
  // bloom shader
  locBloomColorMap    = shaderBloom.getUniformLocation("colorMap");
  
  // initialize our TUIO client
  tuioClient = new TuioProcessing(this,20000);
}

void draw()
{
  background(0);
  //camera(0,0,0,0,0,-1,0,1,0);
  // do some OpenGL setup
  gl.glEnable(gl.GL_LIGHTING);
  gl.glEnable(gl.GL_LIGHT0);
  
  // store how long the application has been running for, in seconds.
  elapsedTime = (float)millis() / 1000.0;
  
  // retrieve our OpenGL rendering context, and begin rendering calls
  PGraphicsOpenGL pgl = (PGraphicsOpenGL) g;
  gl = pgl.beginGL();
  
  // grab all our TUIO cursors
  Vector tuioCursors = tuioClient.getTuioCursors();
  if(tuioCursors.size() > 0) {
    // grab the first tuio cursor
    TuioCursor tuioCursor = (TuioCursor)tuioCursors.elementAt(0);
    // grab its position and convert to our screen coordinates
    TuioPoint pos = tuioCursor.getPosition();
    lightPos[0] = pos.getX() * width - width / 2.0;
    lightPos[1] = pos.getY() * height - height / 2.0;
  }
  
  // move the light source
  //lightPos[0]  = sin(elapsedTime) * 500.0;
  //lightPos[1]  = cos(elapsedTime) * 500.0;
  // this is a temporary hack to correct mouse coordinates - will investigate soon
  //lightPos[0]  = mouseX - width / 2.0; 
  //lightPos[1]  = mouseY - height / 2.0;
  gl.glLightfv(gl.GL_LIGHT0, gl.GL_POSITION, lightPos, 0);
  
  
  //////////////////////////////////////////////////////////////
  // Step 1: render our scene to the off-screen surface (glos)
  
  glos.beginDraw();
  
  // bind our necessary textures to the appropriate texture units
  glos.gl.glActiveTexture(gl.GL_TEXTURE0);
  glos.gl.glEnable(gl.GL_TEXTURE_2D);
  glos.gl.glBindTexture( gl.GL_TEXTURE_2D, tex0.getTextureID() ); // color texture
  
  glos.gl.glActiveTexture(gl.GL_TEXTURE1);
  glos.gl.glEnable(gl.GL_TEXTURE_2D);
  glos.gl.glBindTexture( gl.GL_TEXTURE_2D, tex1.getTextureID() ); // normal texture
  
  // switch the active texture unit back to default (0)
  glos.gl.glActiveTexture(gl.GL_TEXTURE0);
  
  // begin bump shader application
  shaderBump.start();
  
  // upload our application variables to shader memory
  glos.gl.glUniform1i(locUniColorMap,0);   // texture unit containing color texture
  glos.gl.glUniform1i(locUniNormalMap,1);  // texture unit containing normal texture
  glos.gl.glUniform1f(locUniInvRadius,invRadius);
  
  // draw our goldfish texture onto the off-screen surface
  glos.image( tex0, 0, 0, width, height );

  // end shader application
  shaderBump.stop();
  
  // begin bloom shader application
  shaderBloom.start();
  
  // upload our application variables to shader memory
  glos.gl.glUniform1i(locBloomColorMap,0);   // texture unit containing color texture
  
  // render the off-screen's surface back onto itself, applying bloom shader
  glos.image( glos.getTexture(), 0, 0, width, height );
  
  // end shader application
  shaderBloom.stop();

  glos.endDraw();
  
  
  //////////////////////////////////////////////////////////////
  // Step 2: final render pass - render the off-screen surface on-screen, applying our post-processing shader

  // bind our necessary textures
  gl.glActiveTexture(gl.GL_TEXTURE0);
  gl.glEnable(gl.GL_TEXTURE_2D);
  gl.glBindTexture( gl.GL_TEXTURE_2D, glos.getTexture().getTextureID() );

  // apply shader to the following render calls
  shaderPost.start();
  
  // upload our application variables to shader memory
  gl.glUniform1i(texUnit0Loc,0);  // we pass in 0 as the value because we are storing our texture
                                  // in texture unit 0.
  gl.glUniform1f(elapsedTimeLoc,elapsedTime);
  
  // render off-screen surface on-screen
  image(glos.getTexture(),0,0);
  
  // stop shader from being applied to render calls
  shaderPost.stop();


  // conclude rendering calls
  pgl.endGL();
}

// setup our TUIO callback functions

// called when an object is added to the scene
void addTuioObject(TuioObject tobj) {
}

// called when an object is removed from the scene
void removeTuioObject(TuioObject tobj) {
}

// called when an object is moved
void updateTuioObject (TuioObject tobj) {
}

// called when a cursor is added to the scene
void addTuioCursor(TuioCursor tcur) {
}

// called when a cursor is moved
void updateTuioCursor (TuioCursor tcur) {
  //println("update cursor "+tcur.getCursorID()+" ("+tcur.getSessionID()+ ") " +tcur.getX()+" "+tcur.getY()
    //      +" "+tcur.getMotionSpeed()+" "+tcur.getMotionAccel());
}

// called when a cursor is removed from the scene
void removeTuioCursor(TuioCursor tcur) {
}

// called after each message bundle
// representing the end of an image frame
void refresh(TuioTime bundleTime) { 
  redraw();
}
